﻿/* 
 * Author Copyright: Billy McCafferty. 
 * Copied or heavily influenced by : http://www.codeproject.com/KB/architecture/NHibernateBestPractices.aspx  
 * Released under: The Code Project Open License (CPOL) 1.02 (See http://www.codeproject.com/info/cpol10.aspx)
 */

using System;
using System.Collections.Generic;

using NHibernate;
using NHibernate.Criterion;
using Twipler.Data.Session;

namespace Twipler.Data.Access
{
    // Twipler - 
    // Since all my entities derive from Data.Entity.Base and that implements UserCanModify etc then
    // we can enforce create/update/delete security at this level.
    public abstract class AbstractDaoDataEntity<T, IdT> : AbstractNHibernateDao <T, IdT> where T : Data.Entity.Base
    {
        public override T Save(T entity)
        {
            if ((entity.Id == 0) && (entity.UserCanCreate() == false))
                return null;

            if (entity.UserCanModify())
                return base.Save(entity);
            
            return entity;
        }

        public override T SaveOrUpdate(T entity)
        {
            if ((entity.Id == 0) && (entity.UserCanCreate() == false))
                return null;

            if (entity.UserCanModify())
                return base.SaveOrUpdate(entity);
            
            return entity;
        }

        public override void Delete(T entity)
        {   
            if (entity.UserCanModify())
                base.Delete(entity);
        }

    }

    public abstract class AbstractNHibernateDao<T, IdT> : IDao<T, IdT>
    {
        /// <summary>
        /// Loads an instance of type TypeOfListItem from the DB based on its ID.
        /// </summary>
        public T GetById(IdT id, bool shouldLock)
        {
            T entity;

            if (shouldLock)
            {
                entity = (T)NHibernateSession.Get(persitentType, id, LockMode.Upgrade);
            }
            else
            {
                entity = (T)NHibernateSession.Get(persitentType, id);
            }

            return entity;
        }

        /// <summary>
        /// Loads an instance of type TypeOfListItem from the DB based on its ID.
        /// </summary>
        public T LoadById(IdT id, bool shouldLock)
        {
            T entity;

            if (shouldLock)
            {
                entity = (T)NHibernateSession.Load(persitentType, id, LockMode.Upgrade);
            }
            else
            {
                entity = (T)NHibernateSession.Load(persitentType, id);
            }

            return entity;
        }

        /// <summary>
        /// Loads every instance of the requested type with no filtering.
        /// </summary>
        public List<T> GetAll()
        {
            return GetByCriteria();
        }

        /// <summary>
        /// Loads every instance of the requested type with no filtering, ordered by propertyName
        /// </summary>
        public List<T> GetAll(string propertyName, bool ascending)
        {
            ICriteria criteria = NHibernateSession.CreateCriteria(persitentType);
            criteria.AddOrder(new Order(propertyName, ascending));

            return criteria.List<T>() as List<T>;
        }

        /// <summary>
        /// Loads every instance of the requested type using the supplied <see cref="ICriterion" />.
        /// If no <see cref="ICriterion" /> is supplied, this behaves like <see cref="GetAll" />.
        /// </summary>
        public List<T> GetByCriteria(string propertyName, bool ascending, List<ICriterion> criterion)
        {
            ICriteria criteria = NHibernateSession.CreateCriteria(persitentType);
            criteria.AddOrder(new Order(propertyName, ascending));

            foreach (ICriterion criterium in criterion)
                criteria.Add(criterium);

            return criteria.List<T>() as List<T>;
        }

        /// <summary>
        /// Loads every instance of the requested type using the supplied <see cref="ICriterion" />.
        /// If no <see cref="ICriterion" /> is supplied, this behaves like <see cref="GetAll" />.
        /// </summary>
        public List<T> GetByCriteria(params ICriterion[] criterion)
        {
            ICriteria criteria = NHibernateSession.CreateCriteria(persitentType);

            foreach (ICriterion criterium in criterion)
            {
                criteria.Add(criterium);
            }

            return criteria.List<T>() as List<T>;
        }

        public ICriteria Criteria(params ICriterion[] criterion)
        {
            ICriteria criteria = NHibernateSession.CreateCriteria(persitentType);

            foreach (ICriterion criterium in criterion)
            {
                criteria.Add(criterium);
            }

            return criteria;
        }

        public List<T> GetByExample(T exampleInstance, params string[] propertiesToExclude)
        {
            ICriteria criteria = NHibernateSession.CreateCriteria(persitentType);
            Example example = Example.Create(exampleInstance);

            foreach (string propertyToExclude in propertiesToExclude)
            {
                example.ExcludeProperty(propertyToExclude);
            }

            criteria.Add(example);

            return criteria.List<T>() as List<T>;
        }

        /// <summary>
        /// Looks for a single instance using the example provided.
        /// </summary>
        /// <exception cref="NonUniqueResultException" />
        public T GetUniqueByExample(T exampleInstance, params string[] propertiesToExclude)
        {
            List<T> foundList = GetByExample(exampleInstance, propertiesToExclude);

            if (foundList.Count > 1)
            {
                throw new NonUniqueResultException(foundList.Count);
            }

            if (foundList.Count > 0)
            {
                return foundList[0];
            }
            else
            {
                return default(T);
            }
        }

        /// <summary>
        /// For entities that have assigned ID's, you must explicitly call Save to add a new one.
        /// See http://www.hibernate.org/hib_docs/reference/en/html/mapping.html#mapping-declaration-id-assigned.
        /// </summary>
        public virtual T Save(T entity)
        {
            NHibernateSession.Save(entity);
            NHibernateSession.Flush();
            return entity;
        }

        /// <summary>
        /// For entities with automatatically generated IDs, such as identity, SaveOrUpdate may 
        /// be called when saving a new entity.  SaveOrUpdate can also be called to update any 
        /// entity, even if its ID is assigned.
        /// </summary>
        public virtual T SaveOrUpdate(T entity)
        {
            NHibernateSession.SaveOrUpdate(entity);
            NHibernateSession.Flush();
            
            return entity;
        }

        public virtual void Delete(T entity)
        {
            NHibernateSession.Delete(entity);
            NHibernateSession.Flush();
        }

        /// <summary>
        /// Commits changes regardless of whether there's an open transaction or not
        /// </summary>
        public void CommitChanges()
        {
            if (NHibernateSessionManager.Instance.HasOpenTransaction())
            {
                NHibernateSessionManager.Instance.CommitTransaction();
            }
            else
            {
                // If there's no transaction, just flush the changes
                NHibernateSessionManager.Instance.GetSession().Flush();
            }
        }

        /// <summary>
        /// Exposes the ISession used within the DAO.
        /// </summary>
        private ISession NHibernateSession
        {
            get
            {
                return NHibernateSessionManager.Instance.GetSession();
            }
        }

        private Type persitentType = typeof(T);
    }
}
